using System;
using System.IO;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.Text;
using System.Net;

using LumiSoft.Net.SIP.Message;

namespace LumiSoft.Net.SIP.Stack
{
    /// <summary>
    /// SIP server request. Related RFC 3261.
    /// </summary>
    public class SIP_Request : SIP_Message
    {
        private string     m_Method     = "";
        private string     m_Uri        = "";
        private string     m_SipVersion = "";
        private IPEndPoint m_pLocalEP   = null;
        private IPEndPoint m_pRemoteEP  = null;

        /// <summary>
        /// Default constructor.
        /// </summary>
        public SIP_Request()
        {
            m_SipVersion = "SIP/2.0";
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="method">SIP request method.</param>
        public SIP_Request(string method)
        {
            m_Method     = method;
            m_SipVersion = "SIP/2.0";
        }


        #region method Copy

        /// <summary>
        /// Clones this request.
        /// </summary>
        /// <returns>Returns new cloned request.</returns>
        public SIP_Request Copy()
        {
            SIP_Request retVal = SIP_Request.Parse(this.ToByteData());
            retVal.LocalEndPoint  = m_pLocalEP;
            retVal.RemoteEndPoint = m_pRemoteEP;

            return retVal;
        }

        #endregion


        #region method Validate

        /// <summary>
        /// Checks if SIP request has all required values as request line,header fields and their values.
        /// Throws Exception if not valid SIP request.
        /// </summary>
        public void Validate()
        {
            // Request SIP version
            // Via: + branch prameter
            // To:
            // From:
            // CallID:
            // CSeq
            // Max-Forwards RFC 3261 8.1.1.

            if(!m_SipVersion.ToUpper().StartsWith("SIP/2.0")){
                throw new SIP_ParseException("Not supported SIP version '" + m_SipVersion + "' !");
            }

            if(this.Via.GetTopMostValue() == null){
                throw new SIP_ParseException("Via: header field is missing !");
            }
            if(this.Via.GetTopMostValue().Branch == null){
                throw new SIP_ParseException("Via: header field branch parameter is missing !");
            }

            if(this.To == null){
                throw new SIP_ParseException("To: header field is missing !");
            }

            if(this.From == null){
                throw new SIP_ParseException("From: header field is missing !");
            }

            if(this.CallID == null){
                throw new SIP_ParseException("CallID: header field is missing !");
            }

            if(this.CSeq == null){
                throw new SIP_ParseException("CSeq: header field is missing !");
            }

            if(this.MaxForwards == -1){
                // We can fix it by setting it to default value 70.
                this.MaxForwards = 70;
            }


            /* RFC 3261 12.1.2
                When a UAC sends a request that can establish a dialog (such as an INVITE) it MUST 
                provide a SIP or SIPS URI with global scope (i.e., the same SIP URI can be used in 
                messages outside this dialog) in the Contact header field of the request. If the 
                request has a Request-URI or a topmost Route header field value with a SIPS URI, the
                Contact header field MUST contain a SIPS URI.
            */
            if(SIP_Utils.MethodCanEstablishDialog(this.Method)){
                if(this.Contact.GetAllValues().Length == 0){
                    throw new SIP_ParseException("Contact: header field is missing, method that can establish a dialog MUST provide a SIP or SIPS URI !");
                }
                if(this.Contact.GetAllValues().Length > 1){
                    throw new SIP_ParseException("There may be only 1 Contact: header for the method that can establish a dialog !");
                }
                if(!this.Contact.GetTopMostValue().Address.IsSipOrSipsUri){
                    throw new SIP_ParseException("Method that can establish a dialog MUST have SIP or SIPS uri in Contact: header !");
                }
            }

            // TODO: Invite must have From: tag

            // TODO: Check that request-Method equals CSeq method

            // TODO: PRACK must have RAck and RSeq header fields.

            // TODO: get in transport made request, so check if sips and sip set as needed.
        }

        #endregion

        #region method Parse

        /// <summary>
        /// Parses SIP_Request from byte array.
        /// </summary>
        /// <param name="data">Valid SIP request data.</param>
        /// <returns>Returns parsed SIP_Request obeject.</returns>
        /// <exception cref="ArgumentNullException">Raised when <b>data</b> is null.</exception>
        /// <exception cref="SIP_ParseException">Raised when invalid SIP message.</exception>
        public static SIP_Request Parse(byte[] data)
        {
            if(data == null){
                throw new ArgumentNullException("data");
            }

            return Parse(new MemoryStream(data));
        }

        /// <summary>
        /// Parses SIP_Request from stream.
        /// </summary>
        /// <param name="stream">Stream what contains valid SIP request.</param>
        /// <returns>Returns parsed SIP_Request obeject.</returns>
        /// <exception cref="ArgumentNullException">Raised when <b>stream</b> is null.</exception>
        /// <exception cref="SIP_ParseException">Raised when invalid SIP message.</exception>
        public static SIP_Request Parse(Stream stream)
        {
            /* Syntax:
                SIP-Method SIP-URI SIP-Version
                SIP-Message                          
            */

            if(stream == null){
                throw new ArgumentNullException("stream");
            }

            SIP_Request retVal = new SIP_Request();

            // Parse Response-line
            StreamLineReader r = new StreamLineReader(stream);
            r.Encoding = "utf-8";
            string[] method_uri_version = r.ReadLineString().Split(' ');
            if(method_uri_version.Length != 3){
                throw new Exception("Invalid SIP request data ! Method line doesn't contain: SIP-Method SIP-URI SIP-Version.");
            }
            retVal.m_Method     = method_uri_version[0];
            retVal.m_Uri        = method_uri_version[1];
            retVal.m_SipVersion = method_uri_version[2];

            // Parse SIP message
            retVal.InternalParse(stream);

            return retVal;
        }

        #endregion

        #region method ToStream

        /// <summary>
        /// Stores SIP_Request to specified stream.
        /// </summary>
        /// <param name="stream">Stream where to store.</param>
        public void ToStream(Stream stream)
        {
            // Request-Line = Method SP Request-URI SP SIP-Version CRLF

            // Add request-line
            byte[] responseLine = Encoding.UTF8.GetBytes(m_Method + " " + m_Uri + " " + m_SipVersion + "\r\n");
            stream.Write(responseLine,0,responseLine.Length);

            // Add SIP-message
            this.InternalToStream(stream);
        }

        #endregion

        #region method ToByteData

        /// <summary>
        /// Converts this request to raw srver request data.
        /// </summary>
        /// <returns></returns>
        public byte[] ToByteData()
        {
            MemoryStream retVal = new MemoryStream();
            ToStream(retVal);

            return retVal.ToArray();
        }

        #endregion


        #region method CreateResponse

        /// <summary>
        /// Creates response to this request.
        /// </summary>
        /// <param name="statusCode_ReasonPhrase">SIP Status-Code with Reason-Phrase (Status-Code SP Reason-Phrase).</param>
        /// <returns>Returns SIP response with specified code and copy of all original headers from this request.</returns>
        /// <exception cref="InvalidOperationException">Is raised when request is ACK(ACK has no response).</exception>
        public SIP_Response CreateResponse(string statusCode_ReasonPhrase)
        {
            /* RFC 3261 8.2.6.
                The From field of the response MUST equal the From header field of
                the request.  The Call-ID header field of the response MUST equal the
                Call-ID header field of the request.  The CSeq header field of the
                response MUST equal the CSeq field of the request.  The Via header
                field values in the response MUST equal the Via header field values
                in the request and MUST maintain the same ordering.

                If a request contained a To tag in the request, the To header field
                in the response MUST equal that of the request.  However, if the To
                header field in the request did not contain a tag, the URI in the To
                header field in the response MUST equal the URI in the To header
                field; additionally, the UAS MUST add a tag to the To header field in
                the response (with the exception of the 100 (Trying) response, in
                which a tag MAY be present).  This serves to identify the UAS that is
                responding, possibly resulting in a component of a dialog ID.  The
                same tag MUST be used for all responses to that request, both final
                and provisional (again excepting the 100 (Trying)).
            */

            if(this.Method == SIP_Methods.ACK){
                throw new InvalidOperationException("ACK is responseless request !");
            }

            SIP_Response response = new SIP_Response();
            response.StatusCode_ReasonPhrase = statusCode_ReasonPhrase;
            foreach(SIP_t_ViaParm via in this.Via.GetAllValues()){
                response.Via.Add(via.ToStringValue());
            }
            response.From   = this.From;
            response.To     = this.To;
            response.CallID = this.CallID;
            response.CSeq   = this.CSeq;

            // Add Allow to non ACK responses (ACK has no response).
            // We block ACK above, so all responses here are valid.
            response.Allow.Add("INVITE,ACK,OPTIONS,CANCEL,BYE,PRACK");

            // Add Allow to non ACK 2xx responses (ACK has no response).
            // We block ACK above, so all 2xx responses here are valid.
            if(response.StatusCodeType == SIP_StatusCodeType.Success){
                response.Supported.Add("100rel,timer");
            }
            
            return response;
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets request method(REGISTER,INVITE,...) related to this request. This is always in upper-case.
        /// </summary>
        public string Method
        {
            get{ return m_Method.ToUpper(); }

            set{
                if(string.IsNullOrEmpty(value)){
                    throw new ArgumentNullException("Property SipMethod value can't be '' or null !");
                }

                m_Method = value;
            }
        }
        
        /// <summary>
        /// Gets request URI.
        /// </summary>
        public string Uri
        {
            get{ return m_Uri; }

            set{
                m_Uri = value; 
            }
        }

        /// <summary>
        /// Gets or sets SIP request version. Normally this must be always 'SIP/2.0'.
        /// </summary>
        public string SipVersion
        {
            get{ return m_SipVersion; }

            set{
                if(string.IsNullOrEmpty(value)){
                    throw new ArgumentException("Property SipVersion can't be '' or null !");
                }

                m_SipVersion = value;
            } 
        }

                
        /// <summary>
        /// Gets or sets local end point what received this request. Returns null if this request isn't received one !.
        /// </summary>
        internal IPEndPoint LocalEndPoint
        {
            get{ return m_pLocalEP; }

            set{ m_pLocalEP = value; }
        }

        /// <summary>
        /// Gets or sets remote end point what sent this request. Returns null if this request isn't received one !.
        /// </summary>
        internal IPEndPoint RemoteEndPoint
        {
            get{ return m_pRemoteEP; }

            set{ m_pRemoteEP = value; }
        }

        #endregion

    }    
}
